// Description: 错误类
package xerror

import (
	"context"
	"fmt"
	"runtime"
	"strconv"
	"strings"

	"github.com/zeromicro/go-zero/core/logx"
)

type (
	Error interface {
		error

		Code() int64
		Message() string
		String() string
		SetIsLog(isLog bool) Error
		Log(ctx context.Context, prefix ...string)

		// 设置错误码
		SetCode(code int64) Error
		// 设置错误消息
		SetMessage(message string) Error
		// 附加一些信息
		AttachData(value interface{}) Error
	}

	defaultError struct {
		code    int64
		message string
		data    []string
		isLog   bool
	}
)

func NewError() Error {
	return &defaultError{
		data:  []string{},
		isLog: true,
	}
}

func ErrMsg(err error, msg ...string) Error {
	if err == nil {
		inErr := &defaultError{
			data:  []string{},
			isLog: true,
		}
		if len(msg) > 0 {
			return inErr.InterAttachData(msg[0]).SetMessage(msg[0])
		}
		return inErr
	}

	switch e := err.(type) {
	case Error:
		if len(msg) > 0 {
			return e.(*defaultError).InterAttachData(msg[0]).SetMessage(msg[0])
		}
		return e
	default:
		inErr := &defaultError{
			data:  []string{},
			isLog: true,
		}
		if len(msg) > 0 {
			return inErr.InterAttachData(e).SetMessage(msg[0])
		}
		return inErr.InterAttachData(e)
	}
}

func (e *defaultError) SetCode(code int64) Error {
	e.code = code
	return e
}

func (e *defaultError) SetMessage(message string) Error {
	e.message = message
	return e
}

func (e *defaultError) AttachData(value interface{}) Error {
	e.data = append(e.data, fmt.Sprintf("%s: %v, ", getCaller(), value))
	return e
}

func (e *defaultError) InterAttachData(value interface{}) Error {
	e.data = append(e.data, fmt.Sprintf("%s: %v, ", getCaller(3), value))
	return e
}

func (e *defaultError) SetIsLog(isLog bool) Error {
	e.isLog = isLog
	return e
}

func (e *defaultError) Log(ctx context.Context, prefix ...string) {
	if e.isLog {
		if len(prefix) > 0 {
			logx.WithContext(ctx).Error(prefix[0], e)
		} else {
			logx.WithContext(ctx).Error(e)
		}
	} else {
		if len(prefix) > 0 {
			logx.WithContext(ctx).Info(prefix[0], e)
		} else {
			logx.WithContext(ctx).Info(e)
		}
	}
}

func (e *defaultError) Error() string {
	return e.String()
}

func (e *defaultError) String() string {
	var s strings.Builder

	for _, v := range e.data {
		s.WriteString(v)
	}

	s.WriteString(fmt.Sprintf("%d: %s", e.code, e.message))

	return s.String()
}

func (e *defaultError) Code() int64 {
	return e.code
}

func (e *defaultError) Message() string {
	return e.message
}

func getCaller(skips ...int) string {
	var buf strings.Builder

	skip := 2
	if len(skips) > 0 {
		skip = skips[0]
	}

	_, file, line, ok := runtime.Caller(skip)
	if ok {
		short := file
		for i := len(file) - 1; i > 0; i-- {
			if file[i] == '/' {
				short = file[i+1:]
				break
			}
		}
		buf.WriteString(short)
		buf.WriteByte(':')
		buf.WriteString(strconv.Itoa(line))
	}

	return buf.String()
}
